package ig.finanzas;

import ig.metrica.escenario.GeneraEscenarioBean;
import ig.util.ConexionServer;
import ig.util.ejb.IGEntityBean;
import ig.util.excepciones.DatoInvalidoExcepcion;
import ig.util.excepciones.LogicaExcepcion;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.Properties;
import javax.ejb.FinderException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/**
 * Representa un negocio financiero entre dos partes, que bien pueden ser entidades del sector
 * o personas naturales o juridicas, y que poseen condiciones particulares.
 * @author lfgarcia
 */
public abstract class ContratoBean extends IGEntityBean {

    protected MonedaPK monedaPK;
    protected ProductoPK productoPK;
    protected ManejadorFlujos flujos;
    protected ManejadorValoresHistoricos valoresRegistrados;
    protected ManejadorTasasPago tasasDePago;

    public MonedaPK getMonedaPK() {
        return monedaPK;
    }

    public ProductoPK getProductoPK() {
        return productoPK;
    }

    /*
     * --------------------------------------------------------------------------------------------------------------------------
     * Los siguientes metodos permiten la carga de los valores de determinados atributos desde la
     * base de datos. Puede que muchos de ellos no apliquen para todas las subclases de ContratoBean
     * pero para evitar la redundancia de dicho codigo en todas las clases se prefirio dejarlo aqui
     * y tomar la determinacion para cada subclase de contrato que si se requiere del metodo se usa
     * y de lo contrato no.
     */
    /**
     * Carga la llave del producto de un contrato dada el identificador interno del contrato
     * dentro del sistema.
     * @param idInterna
     * @throws SQLException Si se presenta algun problema con la base de datos.
     */
    protected void cargarProductoPK(Long idInterna) throws SQLException {
        ResultSet r = null;
        boolean isClose = false;
        try {
            this.closeStatemente(pstmt1);
            if (conn == null || conn.isClosed()) {
                isClose = true;
                try {
                    conn = this.getConexion("METRICADB");
                } catch (NamingException ex) {
                    ex.printStackTrace();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            pstmt1 = conn.prepareStatement(
                    "SELECT DISTINCT L.NOMBRE AS LIBRO, C2.NOMBRE AS CUENTA, P.NOMBRE AS PRODUCTO " +
                    "FROM CONTRATOS C, PRODUCTOS P, CUENTAS C2, LIBROS L " +
                    "WHERE C.IDPRODUCTO=P.ID AND P.IDCUENTA=C2.ID AND C2.IDLIBRO=L.ID AND C.ID=? ");
            pstmt1.setLong(1, idInterna);
            r = pstmt1.executeQuery();
            if (r.next()) {
                this.productoPK = new ProductoPK(r.getString("LIBRO"), r.getString("CUENTA"), r.getString("PRODUCTO"));
            } else {
                System.out.println("Problemas con contrato: " + idInterna);
            }
        } catch (SQLException e) {
            throw e;
        } finally {
            this.closeStatemente(pstmt1);
            try {
                r.close();
            } catch (Exception e) {
            }
            if (isClose) {
                try {
                    conn.close();
                } catch (Exception e) {
                }
            }
        }
    }



    /**
     * Carga los valores registrados de un contrato dado el identificador interno del contrato
     * dentro del sistema.
     * @param idInterna
     * @param factor Especifica un valor que se multiplica por el valor del contrato a una fecha.
     * @throws DatoInvalidoExcepcion Si la fecha es nula o el numero de valores por registro en la
     * fecha especificada no coinciden con los esperados.
     * @throws SQLException Si se presenta algun error con la base de datos.
     */
    protected void cargarValoresRegistrados(long idInterna, double factor) throws DatoInvalidoExcepcion, SQLException {
        ResultSet r = null;
        boolean isClose = false;
        try {
            if (conn == null || conn.isClosed()) {
                isClose = true;
                try {
                    conn = this.getConexion("METRICADB");
                } catch (NamingException ex) {
                    ex.printStackTrace();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            this.closeStatemente(pstmt1);
            pstmt1 = conn.prepareStatement(
                    "SELECT FECHA, VALOR AS VALOR " +
                    "FROM VALORESCONTRATOS " +
                    "WHERE IDCONTRATO=?");

            pstmt1.setLong(1, idInterna);
            r = pstmt1.executeQuery();
            try {
                valoresRegistrados = new MVHSimple();
            } catch (DatoInvalidoExcepcion e) {
            }

            while (r.next()) {
                double[] valores = new double[1];
                valores[0] = r.getDouble("VALOR");
                valoresRegistrados.adicionarValor(r.getDate("FECHA"), valores);
            }
        } catch (DatoInvalidoExcepcion e) {
            throw e;
        } catch (SQLException e) {
            throw e;
        } finally {
            try {
                r.close();
            } catch (Exception e) {
            }
            try {
                pstmt1.close();
            } catch (Exception e) {
            }
            if (isClose) {
                try {
                    conn.close();
                } catch (Exception e) {
                }
            }
        }
    }

    /**
     * Carga las tasas de pago de un contrato dado el identificador interno del contrato en el
     * sistema.
     * @param idInterna
     * @throws SQLException Si se presenta algun error con la base de datos.
     * @throws DatoInvalidoExcepcion Si la fecha de inicio o fin son nulas, o si la forma
     * de la tasa de pago no es valida, o si las modalidades especificadas no poseen valores
     * validos.
     */
    protected void cargarTasasDePago(long idInterna) throws SQLException, DatoInvalidoExcepcion {
        ResultSet r1 = null;
        boolean isClose = false;
        try {
            if (conn == null || conn.isClosed()) {
                isClose = true;
                try {
                    conn = this.getConexion("METRICADB");
                } catch (NamingException ex) {
                    ex.printStackTrace();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            this.closeStatemente(pstmt1);
            pstmt1 = conn.prepareStatement(
                    "SELECT FORMA, SPREAD, FECHAINICIO, FECHAFIN, MODALIDADPAGO, NOMINALMODALIDADPAGO, " +
                    "MODALIDADSPREAD, NOMINALMODALIDADSPREAD, IDTASAREFERENCIA, LIQUIDAFINDEPERIODO " +
                    "FROM TASASDEPAGOS " +
                    "WHERE IDCONTRATO=? ");
            pstmt1.setLong(1, idInterna);
            ArrayList objsTasas = new ArrayList();

            r1 = pstmt1.executeQuery();
            while (r1.next()) {
                if (pstmt2 != null) {
                    pstmt2.close();
                }
                TasaPago tasaPago = null;
                byte forma = r1.getByte("FORMA");
                double spread = r1.getDouble("SPREAD");
                Date fechaInicio = r1.getDate("FECHAINICIO");
                Date fechaFin = r1.getDate("FECHAFIN");
                String modalidadspread = r1.getString("MODALIDADSPREAD");
                boolean nominal1 = (r1.getInt("NOMINALMODALIDADSPREAD") == 1 ? true : false);
                String modalidadpago = r1.getString("MODALIDADPAGO");
                boolean nominal2 = (r1.getInt("NOMINALMODALIDADPAGO") == 1 ? true : false);

                boolean liquidaFinPeriodo;
                TasaPK tasaPK = null;
                if (forma != TasaPago.FORMA_SIMPLE) {
                    liquidaFinPeriodo = (r1.getInt("LIQUIDAFINDEPERIODO") == 1 ? true : false);
                    int idTasa = r1.getInt("IDTASAREFERENCIA");

                    pstmt2 = conn.prepareStatement(
                            "SELECT NOMBRE " +
                            "FROM TASAS " +
                            "WHERE ID=?");
                    pstmt2.setInt(1, idTasa);
                    ResultSet r2 = pstmt2.executeQuery();
                    r2.next();
                    tasaPK = new TasaPK(r2.getString("NOMBRE"));
                    tasaPago = new TasaPago(forma, spread, fechaInicio, fechaFin, tasaPK, modalidadspread, nominal1, modalidadpago, nominal2, liquidaFinPeriodo);
                } else {
                    tasaPago = new TasaPago(spread, fechaInicio, fechaFin, modalidadspread, nominal1, modalidadpago, nominal2);
                }
                objsTasas.add(tasaPago);
            }
            tasasDePago = new ManejadorTasasPago(objsTasas);
        } catch (DatoInvalidoExcepcion e) {
            throw e;
        } catch (SQLException e) {
            throw e;
        } finally {
            try {
                r1.close();
            } catch (Exception e) {
            }
            try {
                pstmt1.close();
            } catch (Exception e) {
            }
            try {
                pstmt2.close();
            } catch (Exception e) {
            }
            if (isClose) {
                try {
                    conn.close();
                } catch (Exception e) {
                }
            }
        }
    }

    //---------------------------------------------------------------------------------------------------------------------------------------
    /**
     * Devuelve la coleccion de los flujos que posee el contrato entre dos fechas determinadas. En caso de
     * especificar la fecha de fin com nula se devuelven los contratos desde la fecha inicial.
     * @param fechaInicio
     * @param fechaInicioCerrada Especifica si la fecha inicial debe ser incluida o no en el calculo.
     * @param fechaFin
     * @param fechaFinCerrada Especifica si la fecha final debe ser incluida o no en el calculo.
     * @return
     * @throws DatoInvalidoExcepcion Si la fecha de inicio es nula.
     */
    public ManejadorFlujos flujosEntre(Date fechaInicio, boolean fechaInicioCerrada, Date fechaFin, boolean fechaFinCerrada) throws DatoInvalidoExcepcion {
        if (fechaFin == null) {
            return flujos.flujosDesde(fechaInicio, fechaInicioCerrada);
        }
        return flujos.flujosEntre(fechaInicio, fechaInicioCerrada, fechaFin, fechaFinCerrada);
    }

    /**
     * Devuelve la coleccion de los flujos que posee el contrato a partir de una fecha determinada.
     * @param fechaInicio
     * @param fechaInicioCerrada Especifica si la fecha inicial debe ser incluida o no en el calculo.
     * @return
     * @throws DatoInvalidoExcepcion Si la fecha de inicio es nula.
     */
    public ManejadorFlujos flujosDesde(Date fechaInicio, boolean fechaInicioCerrada) throws DatoInvalidoExcepcion {
        return flujos.flujosDesde(fechaInicio, fechaInicioCerrada);
    }

    /*
     * ------------------------------------------------------------------------------------------------------------------------------------------
     * Este metodo solo aplica para contratos que manejen flujos como factor principal de operacion.
     */
    
    /**
     * A partir de los flujos futuros a partir de la fecha especificada calcula como quedarian los
     * flujos utiles para el calculo de la duracion. La constitucion de los flujos se hace de acuerdo
     * a la norma que dicta la Super Intendencia Bancaria para tal fin.
     * @param fecha
     * @return
     * @throws LogicaExcepcion Si el contrato no tiene flujos para efectuar la operacion.
     * @throws DatoInvalidoExcepcion Si la fecha especificada es nula.
     */
    public ManejadorFlujos flujosParaDuracion(Date fecha) throws LogicaExcepcion, DatoInvalidoExcepcion {
        if (fecha == null) {
            throw new DatoInvalidoExcepcion("La fecha especificada es nula.");
        }

        ManejadorFlujos flujosfuturos = null;
        try {
            flujosfuturos = flujos.flujosDesde(fecha, false);
        } catch (DatoInvalidoExcepcion e) {
        //Solo falla cuando la fecha es nula o cuando no se crea el ArrayList
        //esto no sucede nunca
        }

//		if (flujosfuturos.numeroFlujos()==0)
//                    System.out.println("Numero de flujos = 0") ;
//			throw new LogicaExcepcion("No hay suficientes datos para efectuar esta operacion.");

        Date fecharepreciacionactiva = flujos.fechaRepreciacion(true);
        Date fecharepreciacionpasiva = flujos.fechaRepreciacion(false);
        if (fecharepreciacionactiva == null) {
            fecharepreciacionactiva = (Date) fecha.clone();
        }

        if (fecharepreciacionpasiva == null) {
            fecharepreciacionpasiva = (Date) fecha.clone();
        }

        ArrayList flujosduracion = new ArrayList();
        double kactivo = 0, ivactivo = 0, ifactivo = 0;
        double kpasivo = 0, ivpasivo = 0, ifpasivo = 0;

        Iterator i = flujosfuturos.getColeccionFlujos().iterator();
        while (i.hasNext()) {
            Flujo flujo = (Flujo) i.next();
            if (flujo.esConocido()) {
                if (flujo.getTotal() >= 0 && flujo.getFecha().getTime() == fecharepreciacionactiva.getTime()) {
                    //Si el flujo es positivo en la fecha de repreciacion activa
                    kactivo += flujo.getCapital();
                    ivactivo = flujo.getInteresVariable();
                    ifactivo = flujo.getInteresFijo();
                } else if (flujo.getTotal() < 0 && flujo.getFecha().getTime() == fecharepreciacionpasiva.getTime()) {
                    //Si el flujo es negativo en la fecha de repreciacion pasiva
                    kpasivo += flujo.getCapital();
                    ivpasivo = flujo.getInteresVariable();
                    ifpasivo = flujo.getInteresFijo();
                } else {
                    //Es solo un flujo conocido
                    flujosduracion.add(flujo);
                }
            } else {
                if (flujo.getTotal() >= 0) {
                    kactivo += flujo.getCapital();
                } else {
                    kpasivo += flujo.getCapital();
                }
                try {
                    flujosduracion.add(new Flujo(flujo.getFecha(), 0, flujo.getInteresFijo(), 0, false, false));
                } catch (DatoInvalidoExcepcion e) {
                //Falla cuando la fecha es nula, aqui no falla
                }
            }
        }
        try {
            flujosduracion.add(new Flujo(fecharepreciacionactiva, kactivo, ifactivo, ivactivo, true, false));
            flujosduracion.add(new Flujo(fecharepreciacionpasiva, kpasivo, ifpasivo, ivpasivo, true, false));
        } catch (DatoInvalidoExcepcion e1) {
        //Falla cuando las fechas son nulas, como ninguna de las fechas utilizadas puede ser nula            
        //aqui no fallara.
        }
        ManejadorFlujos respuesta = null;
        try {
            respuesta = new ManejadorFlujos(flujosduracion);
        } catch (DatoInvalidoExcepcion e) {
        //Falla cuando los flujos es nulo o estan vacios, en este caso no fallara.
        }
        return respuesta;
    }

    
    /**
     * Carga los flujos de un contrato dado el identificador interno del contrato.
     * @param idInterna
     * @throws SQLException Si se presenta un error con la base de datos.
     * @throws DatoInvalidoExcepcion Si alguno de los parametros para la construccion
     * de un flujo es nulo.
     */
    protected void cargarFlujos(long idInterna) throws SQLException, DatoInvalidoExcepcion {
        ResultSet r1 = null;
        boolean isClose = false;
        try {
            if (conn == null || conn.isClosed()) {
                isClose = true;
                try {
                    conn = this.getConexion("METRICADB");
                } catch (NamingException ex) {
                    ex.printStackTrace();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            this.closeStatemente(pstmt1);
            pstmt1 = conn.prepareStatement(
                    "SELECT FECHA, CAPITAL, INTERESFIJO, INTERESVARIABLE, CONOCIDO, FORMULADO " +
                    "FROM FLUJOS " +
                    "WHERE IDCONTRATO=?");
            pstmt1.setLong(1, idInterna);
            r1 = pstmt1.executeQuery();
            ArrayList objsFlujos = new ArrayList();

            while (r1.next()) {
                double capital = r1.getDouble("CAPITAL");
                double interesfijo = r1.getDouble("INTERESFIJO");
                double interesvariable = r1.getDouble("INTERESVARIABLE");
                Date fecha = r1.getDate("FECHA");
                boolean conocido = (r1.getInt("CONOCIDO") == 1 ? true : false);
                boolean formulado = (r1.getInt("FORMULADO") == 1 ? true : false);
//                System.out.println("Contrato:"+idInterna+":"+fecha+","+capital+","+interesfijo+","+interesvariable+","+conocido+","+formulado);
                objsFlujos.add(new Flujo(fecha, capital, interesfijo, interesvariable, conocido, formulado));
            }
            this.flujos = new ManejadorFlujos(objsFlujos);
        } catch (SQLException e) {
            throw e;
        } catch (DatoInvalidoExcepcion e) {
            throw e;
        } finally {
            try {
                r1.close();
            } catch (Exception e) {
            }
            try {
                pstmt1.close();
            } catch (Exception e) {
            }
            if (isClose) {
                try {
                    conn.close();
                } catch (Exception e) {
                }
            }
        }
    }
    
    /**
     * Devuelve un objeto Tasa a partir de una TasaPK.
     * @param tasaPK
     * @return
     * @throws FinderException Si por alguna razon no se pudo encontrar la tasa.
     */
    protected Tasa getTasa(TasaPK tasaPK) throws FinderException {
        Properties prop = new Properties();
        prop.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);

        String Sockec = new ConexionServer().getSocketServer();
        prop.put(Context.PROVIDER_URL, Sockec);


        Tasa tasa = null;
        try {
            InitialContext ctx_ = new InitialContext(prop);

            TasaHome home = (TasaHome) ctx_.lookup("ig.finanzas.Tasa");
            tasa = home.findByPrimaryKey(tasaPK);
        } catch (Exception e) {
            throw new FinderException(e.toString());
        }
        return tasa;
    }
    public void closeStatemente(PreparedStatement pst) {
        try {
            pst.close();
        } catch (Exception e) {
        }
    }

    public void closeStatemente(Statement pst) {
        try {
            pst.close();
        } catch (Exception e) {
        }
    }
}